Skip to content

feat(FM.2): warm observe-only ambient monitor daemon - #699

Merged
fu351 merged 3 commits into
DobermanCore:mainfrom
Maqbool61:fm2-monitor-daemon-237
Sep 17, 2026
Merged

fu351 merged 3 commits into
DobermanCore:mainfrom
Maqbool61:fm2-monitor-daemon-237

Conversation

@Maqbool61

@Maqbool61 Maqbool61 commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Closes #237.

doberman monitor run/doberman monitor status — the warm, observe-only
daemon on top of the FM.1 activity bus. Each tick: polls every
doberman.collectors entry point, emits onto the bus, drains from the
daemon's own saved cursor (crash-resumable, no replay/loss), and scores each
event through the same decide() the live gate uses — recording an alert row
via record_decision(..., source_context_override="ambient:<collector_id>").

Hard rules, each with its own test:

  • Observe-only, structurally — an AST-based test asserts
    monitor/daemon.py never statically imports doberman.auth or
    doberman.proxy.
  • No output may read as "blocked"doberman.explain, doberman.render
    (used by doberman log/status), and doberman tui all detect the
    "ambient:" source-context shape and prefix explanations with
    "observed (not enforced): ", replacing "blocked"/"pending auth" language
    with a neutral alert word. Structured output (--jsonl) keeps the true
    verdict for scripts/SIEMs — only human-facing text is softened.
  • No learning from ambient input — a test snapshots every baseline/
    revealed-preference table before and after a real multi-tick daemon run
    and asserts byte-for-byte equality, plus an import-boundary test that the
    daemon never reaches doberman.subjective.baseline/drift/martingale/
    revealed.
  • A dead daemon changes nothing about inline protection — a dedicated
    test (mirroring test_import_boundaries.py's existing pattern) exercises
    the "Policy core must not depend on the ambient monitor" import-linter
    contract programmatically, not just via CI's lint-imports step.
  • Per-event isolation and the conservative-alert-on-scoring-failure fallback
    (ReasonCode.ambient_scoring_error) are both covered directly.

Also: a heartbeat + single-instance guard (refuses a second monitor run for
the same repo), doberman monitor status for liveness/cursor/backlog, and
docs/changelog updates.

CI: all 12 required checks pass (lint/boundaries/docs parity, package
smoke tests on ubuntu/windows, secret-scan, and the full test matrix across
3.11/3.12/3.13). Locally I saw two failures unrelated to this change — a
GUI-geometry test sensitive to local font metrics, and an integration test
whose system_site_packages venv fixture can't see a project-venv install —
neither reproduces here in CI, confirming they're local-environment-only.

doberman monitor run/status score non-inline activity through the
same decision engine, alert-only, never enforcing.

Closes DobermanCore#237

@fu351 fu351 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Maqbool61! The observe-only boundary and no-learning tests are useful safeguards. Before merging, please keep failed alert writes retryable, retain collector instances across ticks, remove exception payloads from logs, make single-instance admission atomic, and distinguish ambient alerts in the dashboard’s badges and counts. I reproduced lost alerts after a failed insert and duplicate admission during simultaneous starts. Once this lands, I’d love you to take #238 (level-5), which adds the collectors directly on top of your daemon.

…omic admission, dashboard badges

- record_decision returns bool; a failed write holds the cursor back
  instead of losing the alert
- collectors discovered once, retained across ticks
- exception logs carry only the error class, never message/traceback
- single-instance admission via an atomic O_CREAT|O_EXCL lock file
- doberman dash badges/counts distinguish ambient alerts from real
  BLOCK/AUTH
@Maqbool61

Copy link
Copy Markdown
Contributor Author

Thanks @Maqbool61! The observe-only boundary and no-learning tests are useful safeguards. Before merging, please keep failed alert writes retryable, retain collector instances across ticks, remove exception payloads from logs, make single-instance admission atomic, and distinguish ambient alerts in the dashboard’s badges and counts. I reproduced lost alerts after a failed insert and duplicate admission during simultaneous starts. Once this lands, I’d love you to take #238 (level-5), which adds the collectors directly on top of your daemon.

Thanks for the detailed review, @fu351! Addressed all five:

  • Retryable writes: record_decision now returns whether the row
    actually landed. The daemon's cursor only advances past a fully-durable
    batch — if any write fails, the cursor holds and the whole batch retries
    next tick rather than losing the alert.
  • Retained collectors: run_forever discovers collectors once and
    passes the same instances into every tick instead of rebuilding them —
    collector-side state now survives across ticks.
  • No exception payloads in logs: replaced exc_info=True with the
    error class name only (type(exc).__name__), matching the convention
    already used elsewhere in the codebase.
  • Atomic single-instance admission: replaced the heartbeat-freshness
    check with an O_CREAT|O_EXCL lock file (same primitive
    storage/fingerprint.py already uses) — a single OS syscall, so two
    simultaneous starts can't both win. Verified with 8 real concurrent
    threads racing the same repo_root: exactly 1 wins, every time. A stale
    lock from a crashed process is still reclaimed via the heartbeat signal.
  • Dashboard badges/counts: doberman dash's stats card and per-row feed
    badge now distinguish an ambient alert from a real BLOCK/AUTH (a separate
    "ALERT" badge/count), so the recent-BLOCK focal number isn't inflated by
    observations that were never enforced.

Repro'd your two bugs directly before fixing (lost alert after a forced
write failure, and the duplicate-admission race with concurrent threads) —
both are now covered by dedicated tests.

@Maqbool61
Maqbool61 requested a review from fu351 September 17, 2026 06:30
@fu351
fu351 merged commit c906f3f into DobermanCore:main Sep 17, 2026
12 checks passed
@fu351

fu351 commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Merged, thanks @Maqbool61! Having the daemon check whether the row actually landed before it advances the bus cursor was the decision that made this safe, an alert can be retried but never silently lost, and the import-boundary test keeps the monitor from ever reaching the engine. Nothing changed in your code, I only used GitHub's update-branch to bring it level with main before the rebase merge. This closes #237. One gap I left for a follow-up, #703: doberman tune still reads ambient rows into the friction report. The next piece is #238 (level-5), the FM.3 collectors that feed the daemon you just built, would love for you to take it.

@Maqbool61
Maqbool61 deleted the fm2-monitor-daemon-237 branch September 18, 2026 05:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

doberman monitor: the warm observe-only ambient daemon (FM.2)

2 participants